<template>
  <!-- 聊天室页面 -->
  <div id="room-root" class="col-div">
    <!-- header -->
    <div class="row-div header" style="width: 100%; height: 65px; justify-content: space-between">
      <!-- 腾讯云logo -->
      <!-- <div class="row-div logo" style="height: 100%; width: 230px; justify-content: center">
        <img style="height: 23px" src="@/assets/images/logo.png" alt="" />
        <div style="width: 9px"></div>
        <div style="width: 1px; height: 10px; background-color: #d8d8d8"></div>
        <div style="width: 9px"></div>
        <div style="width: 86px; height: 23px; font-size: 18px; color: #333333; line-height: 23px">
          视频通话
        </div>
      </div> -->
      <Device :device-can-select="deviceCanSelect" @switchDevice="switchDevice" />
      <!-- 分享屏幕 退出 按钮 -->
      <div class="row-div" style="height: 100%; width: auto">
        <img
          v-if="!store.isShared"
          class="c-p"
          @click="handleStartShare"
          id="screen-btn"
          style="width: 65px; height: 65px"
          src="@/assets/images/screen-off.png"
          alt="共享屏幕"
          title="共享屏幕"
        />
        <img
          v-if="store.isShared"
          class="c-p"
          @click="handleStopShare"
          id="screen-btn"
          style="width: 65px; height: 65px"
          src="@/assets/images/screen-on.png"
          alt="结束共享"
          title="结束共享"
        />
        <div style="width: 20px"></div>
        <img
          class="c-p"
          @click="handleLeave"
          id="logout-btn"
          style="width: 65px; height: 65px"
          src="@/assets/images/logout.png"
          alt="离开房间"
          title="离开房间"
        />
      </div>
      <!-- 房间号 -->
      <div class="header-roomId">
        <span>会议号: {{ roomId }}</span>
        <el-button
          @click="onCopy"
          :data-clipboard-text="copyValue"
          class="m-l-20 copy"
          type="success"
        >
          复制链接
        </el-button>
      </div>
    </div>
    <!-- content -->
    <div class="row-div main m-t-20" style="height: 100%; width: 100%; padding: 10px">
      <div class="left" style="width: 320px; height: 100%; padding: 10px">
        <!-- 成员列表 -->
        <div class="col-div member-card">
          <div v-if="!store.remoteStreams.length" class="not-user">暂无用户进入会议</div>
          <div v-for="member in store.remoteStreams" :key="member.id_" class="member m-b-20">
            <span>用户标识：{{ member.userId_ }}</span>
            <div class="camera-mic">
              <img
                v-if="member.videoState === 'open'"
                class="camera"
                src="@/assets/images/camera-on.png"
              />
              <img
                v-if="!member.videoState || member.videoState === 'close'"
                class="camera"
                src="@/assets/images/camera-off.png"
              />
              <img
                v-if="member.audioState === 'open'"
                class="mic m-l-20"
                src="@/assets/images/mic-on.png"
              />
              <img
                v-if="!member.audioState || member.audioState === 'close'"
                class="mic m-l-20"
                src="@/assets/images/mic-off.png"
              />
            </div>
          </div>
        </div>
        <!-- 聊天窗口 -->
        <div class="chat m-t-20">
          <div class="chat-title">聊天框</div>
          <div ref="scrollBar" class="chat-content-box">
            <div ref="chatBox" class="chat-content">
              <div v-for="(chat, index) in chatList" :key="index">
                {{ `${'用户' + chat.remoteNickName}说：${chat.msg}` }}
              </div>
            </div>
          </div>
          <div class="chat-bottom">
            <el-input
              @keyup.enter.native="onSendMsg"
              v-model="content"
              placeholder="输入内容"
              clearable
            />
            <el-button @click="onSendMsg" type="primary">发送</el-button>
          </div>
        </div>
      </div>
      <!-- 视频网格 -->
      <div class="video-list" style="height: 100%; flex: 1">
        <!-- 主视频 -->
        <div
          @click="useToggleBigScreen()"
          class="local c-p"
          :class="{ 'local-video-small': !isBigScreen }"
          :id="localStream?.getId()"
          v-show="store.isJoined && isLocalVideoContainerShow"
        >
          <div class="tag">
            <div :class="audioMuted ? 'muteAudio' : 'unmuteAudio'" @click.stop="muteAudio"></div>
            <div :class="videoMuted ? 'muteVideo' : 'unmuteVideo'" @click.stop="muteVideo"></div>
          </div>
          <div v-if="!isFullScreen">
            <span class="change-screen-state">{{ isBigScreen ? '单击收起' : '' }}</span>
            <img
              @click.stop="toggleFullScreen(localStream!.getId())"
              class="full-screen"
              src="@/assets/images/full-screen.png"
              alt="全屏"
              title="全屏"
            />
          </div>
          <div v-if="isBigScreen" class="user-id">用户标识：{{ localStream?.getUserId() }}</div>
        </div>
        <!-- 小视频 -->
        <Player />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus';
import TRTC from 'trtc-js-sdk';
import type { Client, LocalStream } from 'trtc-js-sdk';
import { computed, inject, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import appStore from '@/store/index';
import ShareClient from '@/utils/shareClient';
import Player from './Player.vue';
import { useToggleFullScreen } from '@/hooks/useToggleFullScreen';
import { useRoute, useRouter } from 'vue-router';
import { createRoom, getUserSig, reqIsMeetingExist } from '@/api/video';
import Clipboard from 'clipboard';
import { useUserStore } from '@/store/user';
import { useWebsocketStore } from '@/store/websocket';
import type { Chat } from '../single-meeting/type';
import { SIGNAL_TYPE_MUT_ROOM_CHAT } from '@/constants/constants';
import Device from '@/components/Device/index.vue';

const $bus = inject('$bus');
const store = appStore();

let localClient: Client;
let localStream = ref<LocalStream | null>();
let shareClient: any;
const audioMuted = ref(false);
const videoMuted = ref(false);

const inviteLink = ref<string>();

const { isFullScreen, toggleFullScreen } = useToggleFullScreen();

// 视频收起/展开
const isBigScreen = ref(true); // 当前点击屏幕的状态
function useToggleBigScreen() {
  isBigScreen.value = !isBigScreen.value;
}

const route = useRoute();
const router = useRouter();
const roomId = ref();
const userSig = ref();
const userStore = useUserStore();
const curUserId = computed<string>(() => {
  return userStore.userInfo.userMsg.userId + '';
});

// 控制聊天滚动条
const chatBox = ref();
const scrollBar = ref();
function updateScrollTop() {
  // 更新滚动高度
  const chatBoxHeight = chatBox.value?.scrollHeight;
  scrollBar.value.scrollTop = chatBoxHeight;
  // 等待DOM重新渲染，再次更新滚动高度
  nextTick(() => {
    const chatBoxHeight = chatBox.value?.scrollHeight;
    scrollBar.value.scrollTop = chatBoxHeight;
  });
}

const websocket = ref<WebSocket>();
const websocketStore = useWebsocketStore();
const deviceCanSelect = ref(true);
onMounted(async () => {
  roomId.value = route.query.meetingNo;
  // 如果传递了房间号，则说明房间已被创建，此时只需获取usersig
  if (roomId.value) {
    // 需要先判断会议号是否存在
    reqIsMeetingExist(roomId.value).then((res: any) => {
      if (res.code !== 200) {
        deviceCanSelect.value = false;
        ElMessageBox.confirm('会议号不存在或会议已结束', '提示', {
          confirmButtonText: '确定',
          type: 'error',
        })
          .then(() => {})
          .catch(() => {});
      } else {
        ElMessageBox.alert('点击上方选择视频/音频设备后才会加入会议', '提示');
      }
    });
    // 获取userSig
    const res: any = await getUserSig();
    if (res.code === 200) {
      userSig.value = res.data?.userSig;
    }
  } else {
    // 如果没传递房间号，则需要创建房间
    const res: any = await createRoom({
      type: 'multiple',
    });
    if (res.code === 200) {
      ElMessageBox.alert('点击上方选择视频/音频设备后才会加入会议', '提示');
      roomId.value = res.data.roomId;
      userSig.value = res.data.userSig;
    }
  }
  roomId.value = roomId.value * 1; // 将房间号弄成数字类型
  // 用户选择音视频设备后，再加入、创建本地流及发布会议
  // handleJoin();
  router.replace({
    path: route.path,
    query: {
      meetingNo: roomId.value,
    },
  });
});
// 向后端发送进入房间，以便进行实时聊天
function sendJoin() {
  const msg = {
    cmd: 'join',
    roomId: roomId.value + '',
  };
  websocketStore.sendMessage(JSON.stringify(msg));
}
// 聊天功能
const content = ref<string>('');
const chatList = reactive<Array<any>>([]);
function onMessage(event: any) {
  let msg = event.data;
  // 将指令反序列化为对象
  while (typeof msg === 'string') {
    msg = JSON.parse(msg);
  }
  if (msg.cmd === SIGNAL_TYPE_MUT_ROOM_CHAT) {
    chatList.push(msg);
    updateScrollTop();
  }
}
const onSendMsg = () => {
  if (!store.isJoined) {
    ElMessage({
      message: '请先选择视频/音频设备',
      type: 'warning',
    });
    return;
  }
  const msg = content.value.trim();
  if (!msg) {
    ElMessage({
      message: '请输入内容！',
      type: 'warning',
    });
  } else {
    const transferData: Chat = {
      cmd: SIGNAL_TYPE_MUT_ROOM_CHAT,
      roomId: roomId.value + '',
      msg: content.value,
    };
    websocketStore.sendMessage(JSON.stringify(transferData));
  }
  content.value = '';
};
onBeforeUnmount(() => {
  // 非聊天界面时，就不需要监听了
  websocket.value?.removeEventListener('message', onMessage);
});

// 复制会议链接
const copyValue = computed(() => {
  return `http://localhost:5173/#/multi-meeting?meetingNo=${roomId.value}`;
});
const onCopy = () => {
  const clipboard = new Clipboard('.copy');
  clipboard.on('success', (e) => {
    ElMessage({
      message: '复制成功',
      type: 'success',
    });
    // 释放内存
    clipboard.destroy();
  });
  clipboard.on('error', (e) => {
    // 不支持复制
    ElMessage({
      message: '复制失败，你的浏览器不支持通过该方式进行复制！',
      type: 'success',
    });
    // 释放内存
    clipboard.destroy();
  });
};

const muteAudio = () => {
  if (!audioMuted.value) {
    localStream.value!.muteAudio();
    audioMuted.value = true;
  } else {
    localStream.value!.unmuteAudio();
    audioMuted.value = false;
  }
};

const muteVideo = () => {
  if (!videoMuted.value) {
    localStream.value!.muteVideo();
    videoMuted.value = true;
  } else {
    localStream.value!.unmuteVideo();
    videoMuted.value = false;
  }
};

async function handleStartShare() {
  if (!canShareScreen.value) {
    ElMessage.error('选择音视频设备后才能共享屏幕！');
    return;
  }
  shareClient = new ShareClient({
    sdkAppId: parseInt(store.sdkAppId, 10),
    userId: `share${curUserId.value}`,
    roomId: roomId.value,
    secretKey: store.secretKey,
  });
  try {
    await shareClient.join();
    await shareClient.publish();
    if (shareClient.isJoined && shareClient.isPublished) {
      store.isShared = true;
    }
  } catch (error: any) {}
}

function handleStopShare() {
  ElMessageBox.confirm('确定退出屏幕共享吗', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
  })
    .then(async () => {
      try {
        if (shareClient.isPublished) {
          await shareClient.unpublish();
        }
        if (shareClient.isJoined) {
          await shareClient.leave();
        }
        store.isShared = false;
      } catch (error: any) {}
    })
    .catch(() => {});
}

const isLocalVideoContainerShow = ref<boolean>(false);
async function createLocalStream() {
  try {
    localStream.value = TRTC.createStream({
      userId: curUserId.value,
      audio: true,
      video: true,
      cameraId: store.videoDeviceId,
      microphoneId: store.audioDeviceId,
    });
    localStream.value.setVideoProfile('720p');

    await localStream.value.initialize();

    localStream.value
      .play(localStream.value.getId())
      .then(() => {
        addLocalControlView();
        muteVideo();
        muteAudio();
        // 防止出现了视频画面再关闭视频，期望不出现视频画面
        setTimeout(() => {
          isLocalVideoContainerShow.value = true;
        }, 300);
      })
      .catch((e) => {});
  } catch (error: any) {
    ElMessageBox.alert('选择的音视频设备已被占用，请选择其他设备！', '提示', {
      // if you want to disable its autofocus
      // autofocus: false,
      confirmButtonText: '确定',
      callback: () => {
        window.location.reload();
      },
    });
  }
}

async function handleJoin() {
  // const userSig = store.getUserSig();
  try {
    localClient = TRTC.createClient({
      mode: 'rtc',
      sdkAppId: parseInt(store.sdkAppId, 10),
      userId: curUserId.value,
      userSig: userSig.value,
    });
    installEventHandlers();
    await localClient.join({ roomId: roomId.value });
    store.isJoined = true;
    inviteLink.value = store.createShareLink();
    // 加入成功后再创建连接
    websocket.value = websocketStore.connect();
    sendJoin();
    websocket.value.addEventListener('message', onMessage);
  } catch (error: any) {}
  await createLocalStream();
  await handlePublish();
}

async function handlePublish() {
  if (!store.isJoined) {
    ElMessage({ message: 'call publish()- please join() firstly', type: 'warning' });
    return;
  }
  if (store.isPublished) {
    ElMessage({ message: 'duplicate publish() observed', type: 'warning' });
    return;
  }

  try {
    await localClient.publish(localStream.value!);
    store.isPublished = true;
  } catch (error: any) {}
}

async function handleUnpublish() {
  if (!store.isJoined) {
    ElMessage({ message: 'unpublish() - please join() firstly', type: 'warning' });
    return;
  }
  if (!store.isPublished) {
    ElMessage({ message: 'call unpublish() - you have not published yet', type: 'warning' });
    return;
  }
  try {
    await localClient.unpublish(localStream.value!);
    store.isPublished = false;
  } catch (error: any) {}
}

async function handleLeave() {
  if (!store.isJoined) {
    ElMessage({ message: 'leave() - please join() firstly', type: 'warning' });
    return;
  }
  ElMessageBox.confirm('确定离开当前房间吗，若离开，将关闭当前标签页！', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
  })
    .then(async () => {
      try {
        uninstallEventHandlers();
        if (store.isPublished) {
          await handleUnpublish();
        }
        await localClient.leave();
        store.isJoined = false;
        if (localStream.value) {
          localStream.value.stop();
          localStream.value.close();
          localStream.value = null;
        }
      } catch (error: any) {}
      window.close();
    })
    .catch(() => {});
}
// 控制只有选择了音视频设备后，才可以共享屏幕
const canShareScreen = ref(false);
async function switchDevice({ videoId, audioId }: { videoId: string; audioId: string }) {
  if (videoId && audioId) {
    // await createLocalStream();
    // await handlePublish();
    handleJoin();
    canShareScreen.value = true;
  }
  if (videoId) {
    try {
      await localStream.value!.switchDevice('video', videoId);
    } catch (error: any) {}
  }
  if (audioId) {
    try {
      await localStream.value!.switchDevice('audio', audioId);
    } catch (error: any) {}
  }
}

function addLocalControlView() {
  console.log('addLocalControlView');
}

function installEventHandlers() {
  if (!localClient) {
    return;
  }
  localClient.on('error', handleError);
  localClient.on('client-banned', handleBanned);
  localClient.on('peer-join', handlePeerJoin);
  localClient.on('peer-leave', handlePeerLeave);
  localClient.on('stream-added', handleStreamAdded);
  localClient.on('stream-subscribed', handleStreamSubscribed);
  localClient.on('stream-removed', handleStreamRemoved);
  localClient.on('stream-updated', handleStreamUpdated);
  localClient.on('mute-video', handleMuteVideo);
  localClient.on('mute-audio', handleMuteAudio);
  localClient.on('unmute-video', handleUnmuteVideo);
  localClient.on('unmute-audio', handleUnmuteAudio);
}

function handleMuteVideo(event: any) {
  console.log(`[${event.userId}] mute video`);
  store.remoteStreams.forEach((remoteStream) => {
    if (remoteStream.userId_ === event.userId) {
      remoteStream.videoState = 'close';
    }
  });
}

function handleMuteAudio(event: any) {
  console.log(`[${event.userId}] mute audio`);
  store.remoteStreams.forEach((remoteStream) => {
    if (remoteStream.userId_ === event.userId) {
      remoteStream.audioState = 'close';
    }
  });
}

function handleUnmuteVideo(event: any) {
  console.log(`[${event.userId}] unmute video`);
  store.remoteStreams.forEach((remoteStream) => {
    if (remoteStream.userId_ === event.userId) {
      remoteStream.videoState = 'open';
    }
  });
}

function handleUnmuteAudio(event: any) {
  console.log(`[${event.userId}] unmute audio`);
  store.remoteStreams.forEach((remoteStream) => {
    if (remoteStream.userId_ === event.userId) {
      remoteStream.audioState = 'open';
    }
  });
}

function handleError(error: any) {
  ElMessage({ message: `LocalClient error: ${error.message_}`, type: 'error' });
  console.log(`LocalClient error: ${error.message_}`);
}

function handleBanned(event: any) {
  ElMessage({ message: `Client has been banned for ${event.reason}`, type: 'warning' });
  console.log(`Client has been banned for ${event.reason}`);
}

async function handlePeerJoin(event: any) {
  const { userId } = event;
  console.log('handlePeerJoin', event);

  if (userId !== 'local-screen') {
    console.log(`Peer Client [${userId}] joined`);
  }
}

function handlePeerLeave(event: any) {
  const { userId } = event;
  if (userId !== 'local-screen') {
    console.log(`[${userId}] leave`);
  }
}

function handleStreamAdded(event: any) {
  const remoteStream = event.stream;
  const id = remoteStream.getId();
  const userId = remoteStream.getUserId();

  if (remoteStream.getUserId() === `share_${curUserId.value}`) {
    // don't need to screen shared by us
    localClient.unsubscribe(remoteStream).catch((error: any) => {
      console.log(`unsubscribe failed: ${error.message_}`);
    });
  } else {
    console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
    localClient.subscribe(remoteStream).catch((error: any) => {
      console.log(`subscribe failed: ${error.message_}`);
    });
  }
}

function handleStreamSubscribed(event: any) {
  const remoteStream = event.stream;
  const userId = remoteStream.getUserId();
  console.log(`RemoteStream subscribed: [${userId}]`);
  ($bus as any).emit('stream-subscribed', event);
}

function handleStreamRemoved(event: any) {
  const remoteStream = event.stream;
  const userId = remoteStream.getUserId();
  console.log(`RemoteStream removed: [${userId}]`);
  ($bus as any).emit('stream-removed', event);
}

function handleStreamUpdated(event: any) {
  const remoteStream = event.stream;
  const userId = remoteStream.getUserId();
  console.log(
    `RemoteStream updated: [${userId}] audio:${remoteStream.hasAudio()} video:${remoteStream.hasVideo()}`
  );
}

function uninstallEventHandlers() {
  if (!localClient) {
    return;
  }
  localClient.off('error', handleError);
  localClient.off('error', handleError);
  localClient.off('client-banned', handleBanned);
  localClient.off('peer-join', handlePeerJoin);
  localClient.off('peer-leave', handlePeerLeave);
  localClient.off('stream-added', handleStreamAdded);
  localClient.off('stream-subscribed', handleStreamSubscribed);
  localClient.off('stream-removed', handleStreamRemoved);
  localClient.off('stream-updated', handleStreamUpdated);
  localClient.off('mute-video', handleMuteVideo);
  localClient.off('mute-audio', handleMuteAudio);
  localClient.off('unmute-video', handleUnmuteVideo);
  localClient.off('unmute-audio', handleUnmuteAudio);
}
</script>

<style lang="scss" scoped>
#room-root {
  background-color: #f0f0f0;
  height: 100vh;
}
.row-div,
.col-div {
  display: flex;
  align-items: center;
}
.row-div {
  flex-direction: row;
}
.col-div {
  flex-direction: column;
}
.header {
  position: sticky;
  top: 0;
  z-index: 99;
  background-color: #ffffff;
  .logo {
    display: flex;
    align-items: center;
  }
  &-roomId {
    display: flex;
    align-items: center;
    padding-right: 20px;
    font-size: 14px;
    color: #888888;
  }
}
.main {
  flex: 1;
  align-items: flex-start;
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0.7);
}
.left {
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #ffffff;
  .member-card {
    border: 1px solid #eee;
    width: 100%;
    flex: 1;
    overflow-y: auto;
    flex-shrink: 0;
    .not-user {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }
  .chat {
    display: flex;
    flex-direction: column;
    width: 320px;
    height: 370px;
    border: 2px solid #d9dade81;
    &-title {
      padding: 5px 10px;
      flex-shrink: 0;
      background-color: #d9dade81;
    }
    &-content-box {
      flex: 1;
      background-color: #fff;
      padding: 10px;
      overflow-y: auto;
      .content {
        flex: 1;
      }
    }
    &-bottom {
      flex-shrink: 0;
      height: 32px;
      width: 100%;
      display: flex;
      align-items: center;
    }
  }
}

.member {
  width: 100%;
  padding: 10px 5px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #f6f6f6;
  background-color: #f0f0f0;
  .camera-mic {
    display: flex;
    .camera,
    .mic {
      width: 25px;
      height: 25px;
    }
  }
}
// 控制视频/音频开关
.muteAudio {
  background: url('/src/assets/images/mic-off.png') center center no-repeat;
}
.unmuteAudio {
  background: url('/src/assets/images/mic-on.png') center center no-repeat;
}
.muteVideo {
  background: url('/src/assets/images/camera-off.png') center center no-repeat;
}
.unmuteVideo {
  background: url('/src/assets/images/camera-on.png') center center no-repeat;
}
.video-list {
  display: flex;
  align-items: center;
  flex-direction: column;
  overflow: auto;
  position: relative;
  min-height: 250px;
  .local {
    width: 800px;
    height: 600px;
    margin: 0 0 5px 0;
    position: relative;
    .change-screen-state {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      color: #fff;
      z-index: 9;
      display: none;
    }
    .full-screen {
      position: absolute;
      bottom: 5px;
      right: 5px;
      display: none;
      z-index: 99;
    }
    &:hover .change-screen-state,
    &:hover .full-screen {
      display: inline-block;
    }
    .tag {
      position: absolute;
      bottom: 5px;
      right: 30px;
      height: 25px;
      z-index: 999;
      background: rgba(0, 0, 0, 0.3);
      display: flex;
      padding: 0 4px;
      flex-direction: row-reverse;
    }

    .tag > div {
      height: 25px;
      width: 25px;
      cursor: pointer;
    }
    .user-id {
      position: absolute;
      top: 0;
      left: 0;
      background-color: rgba(255, 255, 255, 0.5);
      padding: 2px;
      z-index: 10;
    }
  }
  .local-video-small {
    position: absolute;
    margin-top: 0;
    right: 0;
    width: 30px;
    height: 150px;
    .tag {
      right: 0px;
      top: 10px;
      flex-direction: column;
      justify-content: space-between;
      height: 50px;
      background: rgba(255, 255, 255, 0);
    }
    .full-screen {
      bottom: 0;
      right: 0;
    }
  }
}
</style>
